﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace Microsoft.MSAS.ManagedAPI
{
    public class URLVariantBuilder
    {        
        // The pointer to the url variants inside the msasapi.  This pointer is created by MsasCreateUrlVariantsW 
        // and passed in for the other methods.  Upon disposing, we should call MsasDestroyUrlVariants with this pointer
        private UIntPtr m_pUrlVariants = UIntPtr.Zero;

        // The number of variants for a url returned by the api
        public int m_variantsCount;

        private const uint ERROR_SUCCESS = MRS_HRESULTS.S_OK;
        private const uint ERROR_INVALID_PARAMETER = 0x80070057;
        private const int LARGEBUFFER = 4096;

        public int VariantCount
        {
            get { return m_variantsCount; }
        }

        /// <summary>
        /// Finalizer that calls dispose
        /// </summary>
        ~URLVariantBuilder()
        {
            Dispose(false);
        }

        /// <summary>
        /// Create a instance of the MsasApi by calling the API and storing the pointer
        /// </summary>
        /// <param name="url"></param>
        public URLVariantBuilder(string url)
        {
            // create the variant vector
            uint result = MsasCreateUrlVariantsW(
                url,
                MRS_Canonical_Flags.MSAS_Canonical_None, 
                out m_pUrlVariants);
            if (result != ERROR_SUCCESS)
            {
                throw new Win32Exception((int)result);
            }

            // store the number of variants
            result = MsasGetNumberVariants(m_pUrlVariants, out m_variantsCount);
            if (result != ERROR_SUCCESS)
            {
                throw new Win32Exception((int)result);
            }
        }

        /// <summary>
        /// Return the information about variant at position number using the Msas API
        /// </summary>
        /// <param name="number"></param>
        /// <returns></returns>
        /// <remarks>This method does not attempt with a larger buffer if the size
        /// limit is exceeded.  Since we plan to rewrite the interface with a CLI
        /// I will leave it this way for now</remarks>
        public UrlVariant GetVariant(int variantNumber)
        {
            if (variantNumber > m_variantsCount-1)
            {
                throw new ArgumentOutOfRangeException("variantNumber");
            }
            
            UrlVariant variant = new UrlVariant();
            StringBuilder buffer;
            UIntPtr requiredSize;
            UIntPtr offset;
            UIntPtr hashSize = (UIntPtr)32;
            byte[] hash = new byte[32];


            // Determine the size of the buffer needed
            uint result = MsasGetVariantUrl(
                m_pUrlVariants,
                (UIntPtr)variantNumber,
                null,
                UIntPtr.Zero,
                out requiredSize,
                out offset,
                hash,
                hashSize);

            Int32 neededBufferSize = (Int32)requiredSize;
            buffer = new StringBuilder(neededBufferSize);

            result = MsasGetVariantUrl(
                m_pUrlVariants,
                (UIntPtr)variantNumber,
                buffer,
                (UIntPtr)buffer.Capacity,
                out requiredSize,
                out offset,
                hash,
                hashSize);

            if (result != ERROR_SUCCESS)
            {
                throw new Win32Exception((int)result);
            }
            variant.CanonicalizedUrl = buffer.ToString();
            variant.offset = (int) offset;
            variant.sha256 = hash;

            return variant;
        }

        #region MSAS Api

        // Provided mainly for managed code compatibility; same functionality as 
        // IRefCanonicalUrl::GetVariantVector().
        //
        //#define MSASAPI_CREATEURLVARIANTSW "MsasCreateUrlVariantsW"
        //HRESULT WINAPI
        //MsasCreateUrlVariantsW(
        //    __in LPCWSTR pszUrl,
        //    __in DWORD dwCanonicalFlags,  // MSAS_CANONICAL_FLAGS
        //    __deref_out MsasApi::IRefUrlVariantVector ** ppUrlVariants
        //    );
        //typedef HRESULT ( WINAPI * PFNMSASAPI_CREATEURLVARIANTSW )(
        //    LPCWSTR,
        //    DWORD,
        //    MsasApi::IRefUrlVariantVector **
        //    );
        [DllImport("msasapi.dll", SetLastError = false, CharSet = CharSet.Unicode)]
        public static extern uint MsasCreateUrlVariantsW(
            string pszUrl,
            MRS_Canonical_Flags dwCanonicalFlags,
            out UIntPtr ppUrlVariants);
        
        //// Provided mainly for managed code compatibility; same functionality as 
        //// IRefUrlVariantVector::Release().
        ////
        //#define MSASAPI_DESTROYURLVARIANTS "MsasDestroyUrlVariants"
        //void WINAPI
        //MsasDestroyUrlVariants(
        //    __in MsasApi::IRefUrlVariantVector * pUrlVariants
        //    );
        //typedef void ( WINAPI * PFNMSASAPI_DESTROYURLVARIANTS )(
        //    MsasApi::IRefUrlVariantVector *
        //    );
        [DllImport("msasapi.dll", SetLastError = false, CharSet = CharSet.Unicode)]
        public static extern uint MsasDestroyUrlVariants(
            UIntPtr pUrlVariants);

        //// Provided mainly for managed code compatibility; same functionality as 
        //// IRefUrlVariantVector::GetNumberVariants().
        ////
        //#define MSASAPI_GETNUMBERVARIANTS "MsasGetNumberVariants"
        //HRESULT WINAPI
        //MsasGetNumberVariants(
        //    __in MsasApi::IRefUrlVariantVector * pUrlVariants,
        //    __out SIZE_T * pceLength
        //    );
        //typedef HRESULT ( WINAPI * PFNMSASAPI_GETNUMBERVARIANTS )(
        //    MsasApi::IRefUrlVariantVector *,
        //    SIZE_T *
        //    );
        [DllImport("msasapi.dll", SetLastError = false, CharSet = CharSet.Unicode)]
        public static extern uint MsasGetNumberVariants(
            UIntPtr pUrlVariants,
            out int pceLength);

        //// Provided mainly for managed code compatibility; same functionality available 
        //// directly off the IRefUrlVariantVector object.
        ////
        //#define MSASAPI_GETVARIANTURL "MsasGetVariantUrl"
        //HRESULT WINAPI
        //MsasGetVariantUrl(
        //    __in MsasApi::IRefUrlVariantVector * pUrlVariants,
        //    __in SIZE_T ceVariant,
        //    __out_ecount_opt(cchCanonicalUrl) LPWSTR pszCanonicalUrl,
        //    __in SIZE_T cchCanonicalUrl,
        //    __out_opt SIZE_T* pcchRequiredSize,
        //    __out_opt SIZE_T* pcchSchemelessOffset,
        //    __out_bcount_opt(cbCanonicalUrlHash) BYTE * pbaCanonicalUrlHash,
        //    __in SIZE_T cbCanonicalUrlHash
        //    );
        //typedef HRESULT ( WINAPI * PFNMSASAPI_GETVARIANTURL )(
        //    MsasApi::IRefUrlVariantVector *,
        //    SIZE_T,
        //    LPCWSTR,
        //    LPWSTR,
        //    SIZE_T,
        //    SIZE_T *,
        //    SIZE_T *,
        //    BYTE *,
        //    SIZE_T
        //    );
        [DllImport("msasapi.dll", SetLastError = false, CharSet = CharSet.Unicode)]
        public static extern uint MsasGetVariantUrl(
            UIntPtr pUrlVariants,
            UIntPtr ceVariant,
            StringBuilder pszCanonicalUrl,
            UIntPtr cchCanonicalUrl,
            out UIntPtr pccRequiredSize,
            out UIntPtr pccSchemelessOffset,
            byte[] pbaCanonicalUrlHash,
            UIntPtr cbCanonicalUrlHash);

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Public Dispose for proactive disposing of the object. Should be called explicitly,
        /// or indirectly through the using statement.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);

            GC.SuppressFinalize(this);
        }

        #endregion

        /// <summary>
        /// Dispose implementation. 
        /// </summary>
        /// <param name="disposing">If disposing is true, then dispose is being done proactively and
        /// all owned managed and native resources should be disposed. If false, then dispose is being done
        /// as part of garbage collection, so only native resourses should be disposed.</param>
        protected void Dispose(bool disposing)
        {
            if (m_pUrlVariants != UIntPtr.Zero)
            {
                MsasDestroyUrlVariants(m_pUrlVariants);
                m_pUrlVariants = UIntPtr.Zero;
            }
        }
    }

    public struct UrlVariant
    {
        public string CanonicalizedUrl;
        public byte [] sha256;
        public int offset;
    }
}
